/*
 * This file is part of the Boring Engine.
 *
 * Copyright (c) 2011 by pf5234 <pf5234@users.sourceforge.net>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package com.googlecode.boringengine.internal;

import com.googlecode.boringengine.Animator;
import com.googlecode.boringengine.Config;
import com.googlecode.boringengine.DefaultFont;
import com.googlecode.boringengine.Font;
import com.googlecode.boringengine.GameEngine;
import com.googlecode.boringengine.GameLoader;
import com.googlecode.boringengine.GameSettings;
import com.googlecode.boringengine.Graphics;
import com.googlecode.boringengine.Input;
import com.googlecode.boringengine.Log;
import com.googlecode.boringengine.Menu;
import com.googlecode.boringengine.PauseMenu;
import com.googlecode.boringengine.Render;
import com.googlecode.boringengine.SoundSystem;
import com.googlecode.boringengine.Sprite;
import com.googlecode.boringengine.Util;
import com.googlecode.boringengine.internal.java2drendering.Java2DCanvas;
import com.googlecode.boringengine.internal.java2drendering.Java2DRenderer;
import com.googlecode.boringengine.internal.openglrendering.OpenGLCanvas;
import com.googlecode.boringengine.internal.openglrendering.OpenGLRenderer;
import com.googlecode.boringengine.lua.LuaInterpreter;
import java.awt.Frame;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;

public class Engine {

    private static Frame f;
    private static GameEngine game;
    private static Input input;
    private static Renderer r;
    private static Timer myTimer;
    private static Exception crash;
    private static Menu menu;
    private static Animator[] circles = new Animator[10];

    public static void init() {
        Config.init();
        GameSettings.init();
        f = new Frame();
        r = new OpenGLRenderer() /*Java2DRenderer()*/;
        OpenGLCanvas c = new OpenGLCanvas();
        input = new Input();
        f.setResizable(false);
        Graphics.init(r);
        Render.init(r);
        DefaultFont.getDefaultFont();
        LuaInterpreter.init();
        SoundSystem.init();
        //Java2DCanvas c = new Java2DCanvas((Java2DRenderer) r);
        f.add(c.getCanvas());
        c.getCanvas().setSize(800, 600);
        c.getCanvas().addKeyListener(input);
        c.getCanvas().addMouseListener(input);
        c.getCanvas().addMouseMotionListener(input);
        f.pack();
        f.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                quit();
                System.exit(0);
            }
        });
        f.setVisible(true);
        c.getCanvas().requestFocus();

        myTimer = new Timer();
        double fps = 1000 / 60.;
        if (System.getProperty("os.name").contains("Windows"))
            fps /= 2;
        myTimer.schedule(new Task(), 1, (long)fps);
    }

    public static void initGame(GameEngine g, GameLoader l) {
        if (game != null) game.quit();
        PauseMenu.clear();
        initPauseMenu();
        game = null;
        Graphics.gameChange(l);
        SoundSystem.gameLoaded(l);
        changeFrameStuff(l);
        g.init(l);
        game = g;
    }

    private static int frozened = 150;
    private static Sprite screen;
    
    private static void update() {
        screen = null;
        frozened = 150;
        if (Input.isPressed(KeyEvent.VK_ESCAPE) && menu == null)
            addMenu(PauseMenu.getInstance());
        try {
            if (crash != null) {
                doCrashUpdate();
                return;
            }
            if (menu != null) {
                menu.update();
                if (menu.isClosed())
                    menu = null;
                else
                    return;
            }
            if (game == null) return;
            LuaInterpreter.update();
            game.update();
        } catch (Exception ex) {
            doCrash(ex);
        }
    }

    private static long[] lastFPS = new long[150];
    private static int framenum = -1;

    public static void draw() {
        if (frozened > 0) frozened--;
        try {
            long nowtime = System.nanoTime();
            framenum += 1; framenum %= 150;
            lastFPS[framenum] = nowtime;
            double fps = 150.0 / ((lastFPS[framenum] - lastFPS[(framenum + 1) % 150]) / 1000000000.0);
            Graphics.setFPS(fps);
            if (crash != null) {
                doCrashDraw();
                return;
            }
            if (game == null) {
                doLoadingScreen();
                return;
            }
            if (frozened == 0) {
                doSpinning();
                return;
            }
            if (menu != null) {
                menu.draw();
                return;
            }
            game.draw();
        } catch (Exception ex) {
            doCrash(ex);
        }
    }

    public static void quit() {
        if (game != null) game.quit();
        GameSettings.deinit();
        Config.deinit();
    }

    public static void uploadSprites() {
        r.uploadSprites();
    }

    public static void addMenu(Menu<?> m) {
        menu = m;
        menu.reset();
    }

    private static void changeFrameStuff(GameLoader l) {
        try {
            f.setIconImage(ImageIO.read(l.getFile(l.icon)));
        } catch (Exception ex) {
            Log.warn("Couldn't get icon! %s", ex);
            if (!(l instanceof GameLoaderEngine))
                changeFrameStuff(new GameLoaderEngine());
        }
        f.setTitle(l.name + " " + l.version);
    }

    private static void doLoadingScreen() {
        Render.clearScreen();
        Graphics.drawRect(0, 0, 800, 600, 0);
        Render.setScale(2);
        Font fo = DefaultFont.getDefaultFont();
        fo.draw(800 - fo.getWidth("Now Loading...")*2, 570, "Now Loading...");
        Render.setScale(1);
    }
    
    private static void doCrash(Exception ex) {
        crash = ex;
        Render.takeScreenshot(GameSettings.configDir + "screenshot" + Util.getDateTime() +
                ".png");
    }

    private static void doCrashUpdate() {
        if (!Input.isPressed(KeyEvent.VK_ENTER)) return;
        Log.fatalException(crash, "");
    }

    private static String[] crashInfo;

    private static void doCrashDraw() {
        Render.unClip();
        Render.clearScreen();
        if (crashInfo == null)
            crashInfo = "Fatal Error, press ENTER to exit\n\n".concat(
                    Util.stringifyException(crash)).split("\n");
        Font font = DefaultFont.getDefaultFont();
        for (int i = 0; i < crashInfo.length; i++)
            font.draw(5, i*15, crashInfo[i]);
    }

    private static void doSpinning() {
        if (screen == null) screen = Render.createSpriteFromScreen(0, 0, 800, 600);
        if (circles[0] == null)
            for (int i = 0; i < circles.length; i++) {
                circles[i] = new Animator("resources/circle", new GameLoaderEngine());
                circles[i].changeAnimation("circle" + i);
            }
        Render.clearScreen();
        Graphics.drawSprite(screen, 0, 0);
        Render.setAlpha(128);
        Graphics.drawRect(0, 0, 800, 600, 0);
        Render.setAlpha(255);
        for (int i = 0; i < circles.length; i++) {
            int rot = i * 360 / circles.length;
            int x = (int) (400 + 32 * Math.cos(Math.toRadians(-rot)));
            int y = (int) (300 + 32 * Math.sin(Math.toRadians(-rot)));
            circles[i].draw(x, y);
        }
    }

    private static void initPauseMenu() {
        GameLoaderEngine q = new GameLoaderEngine();
        PauseMenu.addEngine("Return to game", new Animator("resources/pausemenu/returngame", q),
                new BlankRunnable(), true);
        PauseMenu.addEngine("Quit game", new Animator("resources/pausemenu/quitgame", q),
                new QuitRunnable(), true);
    }
    
    private static class Task extends TimerTask {

        private long[] lastFPS = new long[150];
        private int framenum = -1;

        @Override
        public void run() {
            long nowtime = System.nanoTime();
            framenum += 1; framenum %= 150;
            lastFPS[framenum] = nowtime;
            double fps = 150.0 / ((lastFPS[framenum] - lastFPS[(framenum + 1) % 150]) / 1000000000.0);
            Graphics.setUPS(fps);
            update();
        }
    }

    private static class BlankRunnable implements Runnable {

        public void run() {}

    }

    private static class QuitRunnable implements Runnable {

        public void run() {
            if (game instanceof GameMenu) {
                Engine.quit();
                System.exit(0);
            }
            Engine.initGame(new GameMenu(), new GameLoaderEngine());
        }

    }

}
